﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using FamilyTreeProject.Collections;
using FamilyTreeProject.GEDCOM;
using FamilyTreeProject.GEDCOM.Records;
using FamilyTreeProject.GEDCOM.Structures;
using FamilyTreeProject.GEDCOM.Common;
using FamilyTreeProject.Common;

namespace FamilyTreeProject.Data.GEDCOM
{
    public class GEDCOMRepository<T> : IDisposable, IRepository<T> where T : class
    {
        #region Private Members

        private GEDCOMDocument document;
        private string fileName;
        private const int DEFAULT_TREE_ID = 1;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructs a new GEDCOMRepository instance
        /// </summary>
        /// <param name="fileName">The filename to use for the GEDCOM.</param>
        public GEDCOMRepository(string fileName)
        {
            this.fileName = fileName;
            LoadDocument();
        }

        #endregion

        #region Public Properties

        public string FileName
        {
            get { return fileName; }
            set { fileName = value; }
        }

        #endregion

        #region Implementation of IRepository<T>

        /// <summary>
        /// Adds an item to the database
        /// </summary>
        /// <param name="item">The item to add</param>
        public void Add(T item)
        {
            Requires.ArgumentNotNull<T>(item);

            //Make sure document is loaded
            LoadDocument();
            
            if (item is Individual)
            {
                AddIndividual(item as Individual);
            }

            SaveDocument();
        }

        /// <summary>
        /// Deletes an item from the file
        /// </summary>
        /// <param name="item">The item to delete</param>
        public void Delete(T item)
        {
            Requires.ArgumentNotNull<T>(item);

            //Make sure document is loaded
            LoadDocument();

            if (item is Individual)
            {
                DeleteIndividual(item as Individual);
            }

            SaveDocument();
        }

        /// <summary>
        /// Deletes items from the file
        /// </summary>
        /// <param name="expression">An expression that identifes the items to delete</param>
        public void Delete(Expression<Func<T, bool>> expression)
        {
            foreach (T entity in Find(expression))
                Delete(entity);
        }

        /// <summary>
        /// Finds an item using a passed-in expression lambda
        /// </summary>
        public IQueryable<T> Find(Expression<Func<T, bool>> expression)
        {
            //Make sure document is loaded
            LoadDocument();

            return GetAll().Where(expression);
        }

        /// <summary>
        /// Returns all T records in the repository
        /// </summary>
        public IQueryable<T> GetAll()
        {
            //Make sure document is loaded
            LoadDocument();

            IQueryable<T> records = null;

            if (typeof(T) == typeof(Individual))
            {
                List<Individual> individuals = GetIndividuals();

                records = (IQueryable<T>)individuals.AsQueryable();
            }

            return records;
        }

        /// <summary>
        /// Returns a PagedList of items
        /// </summary>
        /// <param name="pageIndex">zero-based index to be used for lookup</param>
        /// <param name="pageSize">the size of the paged items</param>
        public PagedList<T> GetPaged(int pageIndex, int pageSize)
        {
            //Make sure document is loaded
            LoadDocument();

            return new PagedList<T>(GetAll(), pageIndex, pageSize);
        }

        public void Update(T item)
        {
            //Make sure document is loaded
            LoadDocument();

            if (item is Individual)
            {
                UpdateIndividual(item as Individual);
            }

            SaveDocument();
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            throw new System.NotImplementedException();
        }

        #endregion

        #region Private Methods

        #region Individual Methods

        /// <summary>
        /// Adds an Individual to the Repository
        /// </summary>
        /// <param name="individual">The individual to Add</param>
        private void AddIndividual(Individual individual)
        {
            individual.Id = document.Records.GetNextId(GEDCOMTag.INDI);

            GEDCOMIndividualRecord record = new GEDCOMIndividualRecord(individual.Id);
            GEDCOMNameStructure name = new GEDCOMNameStructure(String.Format("{0} /{1}/", individual.FirstName, individual.LastName), record.Level + 1);

            record.Name = name;
            document.AddRecord(record);
        }

        /// <summary>
        /// Deletes an Individual from the Repository
        /// </summary>
        /// <param name="individual">The individual to Delete</param>
        private void DeleteIndividual(Individual individual)
        {
            GEDCOMIndividualRecord record = document.SelectIndividualRecord(CreateId("I", individual.Id));

            if (record == null)
                //record not in repository
                throw new ArgumentOutOfRangeException();
            else
                document.RemoveRecord(record);
        }

        /// <summary>
        /// Gets the Indivudals
        /// </summary>
        /// <returns>A List of Individuals</returns>
        private List<Individual> GetIndividuals()
        {
            List<Individual> individuals = new List<Individual>();

            //Get all the Individuals
            foreach (GEDCOMIndividualRecord individualRecord in document.IndividualRecords)
            {
                Individual individual = new Individual();

                individual.Id = individualRecord.GetId();
                individual.FirstName = individualRecord.Name.GivenName;
                individual.LastName = individualRecord.Name.LastName;
                individual.Sex = individualRecord.Sex;
               

                //HACK: This is a temporary fix - haven't worked out how to handle multiple 
                //Trees in GEDCOMRepository
                individual.TreeId = DEFAULT_TREE_ID;

                individuals.Add(individual);
            }

            return individuals;
        }

        /// <summary>
        /// Updates an individual in the Repository
        /// </summary>
        /// <param name="individual">The individual to update</param>
        private void UpdateIndividual(Individual individual)
        {
            GEDCOMIndividualRecord record = document.SelectIndividualRecord(CreateId("I", individual.Id));
            GEDCOMNameStructure name = new GEDCOMNameStructure(String.Format("{0} /{1}/", individual.FirstName, individual.LastName), record.Level + 1);

            record.Name = name;
        }

        #endregion

        private string CreateId(string prefix, int id)
        {
            return "@" + prefix + id.ToString() + "@";
        }

        private void LoadDocument()
        {
            if (document == null)
            {
                document = new GEDCOMDocument();
                document.Load(this.fileName);
            }
        }
                                                                            
        private void SaveDocument()
        {
            document.Save(this.fileName);

            //Force document to be reloaded
            document = null;
        } 

        #endregion

    }
}
